package com.solarStruggle.graphics

import java.awt.{Color}
import javax.swing.{ImageIcon}
import util.{ResourceLoader}

/**
 * This trait is used to indicate that a class is capable of generating images
 * to be displayed on the screen.
 */
abstract class ImageGenerator(var width: Int, var height: Int) {
  
  /**
   * Create the image defined by this generator
   */
  def createImage(): BufferedImage

}

/**
 * Generates an image by running a procedure for every pixel, or every n
 * pixels
 */
abstract class ProceduralImageGenerator(width: Int, height: Int, fill: Color, squareSize: Int) 
extends ImageGenerator(width, height) {
  
  def this(width: Int, height: Int, fill: Color) = this(width, height, fill, 1)
 
  override def createImage(): BufferedImage = {
    val img = new BufferedImage(width, height)
    img.clear(fill)
    for(x <- 0 until width / squareSize; y <- 0 until height / squareSize)
      renderPixel(img, x * squareSize, y * squareSize)
    img
  }
  
  def renderPixel(image: BufferedImage, x: Int, y: Int)
}

/**
 * This image generator works by overlaying a set of images on top of each 
 * other, with the first item in the list acting as a base and each item above
 * layering on top of it.
 *
 * <p>This class assumes that all images are the same size.</p>
 */
class LayeredImageGenerator(images: Seq[BufferedImage]) 
extends ImageGenerator(images.first.getWidth, images.first.getHeight) {
   
  override def createImage(): BufferedImage =
    images.drop(1).foldLeft(images.first) { 
      (baseImage, nextImage) =>
        baseImage.getGraphics.drawImage(nextImage, 0, 0, baseImage.getWidth, baseImage.getHeight, null)
        baseImage
    }
}

/**
 * This image generator starts with a base image and then processes it in some
 * way to create a new image.
 */
abstract class ProcessedImageGenerator(baseImage: BufferedImage) 
extends ImageGenerator(baseImage.getWidth, baseImage.getHeight) {
  
  /**
   * Abstract method to process a given image and create a new one.
   */
  def process(image: BufferedImage): Unit
  
  override def createImage(): BufferedImage = {
    val newImage = new BufferedImage(width, height)
    newImage.getGraphics.drawImage(baseImage, 0, 0, width, height, null)
    process(newImage)
    newImage
  }
}

class FileImageGenerator(filename: String)
extends ImageGenerator(0, 0) {
  
  override def createImage(): BufferedImage = {
    val icon = new ImageIcon(ResourceLoader.getResourceURL(filename))
    val image = icon.getImage
    width = icon.getIconWidth
    height = icon.getIconHeight;
    val newImage = new BufferedImage(width, height)
    newImage.getGraphics.drawImage(image, 0, 0, null);
    newImage
  }
  
}

/**
 * Adds a constant value to each of the RGB components of an image
 */
class ImageBrightener(image: BufferedImage, deltaLum: Int) 
extends ProceduralImageGenerator(image.getWidth, image.getHeight, Color.BLACK, 1) {

  override def renderPixel(newImage: BufferedImage, x: Int, y: Int) {
    val oldColor = image.getPixel(x, y)
    val newColor = new Color(
      Math.max(0, Math.min(255, oldColor.getRed + deltaLum)),
      Math.max(0, Math.min(255, oldColor.getGreen + deltaLum)), 
      Math.max(0, Math.min(255, oldColor.getBlue + deltaLum)), 
      oldColor.getAlpha)
    newImage.setPixel(x, y, newColor)
  }
}

/**
 * Multiplies a constant value to each of the RGB components of an image
 */
class ImageMultiplier(image: BufferedImage, deltaLum: Float) 
extends ProceduralImageGenerator(image.getWidth, image.getHeight, Color.BLACK, 1) {
  
  override def renderPixel(newImage: BufferedImage, x: Int, y: Int) {
    val oldColor = image.getPixel(x, y)
    val newColor = new Color(
      Math.max(0, Math.min(255, oldColor.getRed * deltaLum)),
      Math.max(0, Math.min(255, oldColor.getGreen * deltaLum)), 
      Math.max(0, Math.min(255, oldColor.getBlue * deltaLum)), 
      oldColor.getAlpha)
    newImage.setPixel(x, y, newColor)
  }
}

/**
 * Adds two images pixels together
 */
class AdditiveImageCombiner(image1: BufferedImage, image2: BufferedImage) 
extends ProceduralImageGenerator(image1.getWidth, image1.getHeight, Color.BLACK, 1) {
  
  override def renderPixel(newImage: BufferedImage, x: Int, y: Int) {
    val color1 = image1.getPixel(x, y)
    val color2 = image2.getPixel(x, y)
    val newColor = new Color(
      Math.max(0, Math.min(255, color1.getRed + color2.getRed)),
      Math.max(0, Math.min(255, color1.getGreen + color2.getGreen)), 
      Math.max(0, Math.min(255, color1.getBlue + color2.getBlue)), 
      Math.max(0, Math.min(255, color1.getAlpha + color2.getAlpha)))
    newImage.setPixel(x, y, newColor)
  }
}

/**
 * Multiplies two images pixels together
 */
class MultiplicativeImageCombiner(image1: BufferedImage, image2: BufferedImage) 
extends ProceduralImageGenerator(image1.getWidth, image1.getHeight, Color.BLACK, 1) {
  
  override def renderPixel(newImage: BufferedImage, x: Int, y: Int) {
    val color1 = image1.getPixel(x, y)
    val color2 = image2.getPixel(x, y)
    val color2Avg = 
      (color2.getRed.toDouble / 255.0 +
       color2.getGreen.toDouble / 255.0 +
       color2.getBlue.toDouble / 255.0) / 3.0
       
    val newColor = new Color(
      Math.max(0, Math.min(255, (color1.getRed.toDouble * color2Avg).toInt)),
      Math.max(0, Math.min(255, (color1.getGreen.toDouble * color2Avg).toInt)), 
      Math.max(0, Math.min(255, (color1.getBlue.toDouble * color2Avg).toInt)), 
      color1.getAlpha)
    newImage.setPixel(x, y, newColor)
  }
}

